+// Copyright (C) 2024 Rubén Beltrán del Río
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://map.tranquil.systems.
import Cocoa
import SwiftUI
class MapTextEditorController: NSViewController {
@Binding var document: MapDocument
+ var highlightRanges: [Range<String.Index>] {
+ didSet {
+ updateHighlights()
+ }
+ }
+
+ var selectedRange: Int {
+ didSet {
+ updateHighlights()
+ focusOnResult()
+ }
+ }
+
let onChange: () -> Void
private let vertexRegex = MapParsingPatterns.vertex
private let changeDebouncer: Debouncer = Debouncer(seconds: 1)
- init(document: Binding<MapDocument>, onChange: @escaping () -> Void) {
+ init(
+ document: Binding<MapDocument>, highlightRanges: [Range<String.Index>], selectedRange: Int,
+ onChange: @escaping () -> Void
+ ) {
self._document = document
self.onChange = onChange
+ self.highlightRanges = highlightRanges
+ self.selectedRange = selectedRange
super.init(nibName: nil, bundle: nil)
}
scrollView.translatesAutoresizingMaskIntoConstraints = false
- textView.backgroundColor = .ui.background
+ textView.backgroundColor = .UI.background
textView.allowsUndo = true
textView.delegate = self
textView.textStorage?.delegate = self
override func viewDidAppear() {
self.view.window?.makeFirstResponder(self.view)
+ updateHighlights()
+ }
+
+ private var textView: NSTextView? {
+ return (view as? NSScrollView)?.documentView as? NSTextView
+ }
+
+ private func updateHighlights() {
+ if let textView {
+ if let textStorage = textView.textStorage {
+ textStorage.removeAttribute(
+ .backgroundColor, range: NSRange(location: 0, length: textStorage.length))
+
+ for (index, range) in highlightRanges.enumerated() {
+ let nsRange = NSRange(range, in: textStorage.string)
+
+ let color = index == selectedRange ? NSColor.Syntax.highlightMatch : NSColor.Syntax.match
+ textStorage.addAttribute(.backgroundColor, value: color, range: nsRange)
+ }
+
+ textView.needsDisplay = true
+
+ }
+ }
+ }
+
+ private func focusOnResult() {
+ if let textView {
+ if let textStorage = textView.textStorage {
+ if selectedRange < highlightRanges.count {
+ let range = highlightRanges[selectedRange]
+ let nsRange = NSRange(range, in: textStorage.string)
+ textView.scrollRangeToVisible(nsRange)
+ }
+ }
+ }
}
}
for match in matches {
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
+ [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 1))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+ [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 2))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
+ [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 3))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.option], range: match.range(at: 4))
+ [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 4))
}
matches = edgeRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
+ [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 1))
let arrowRange = match.range(at: 2)
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.symbol],
+ [.foregroundColor: NSColor.Syntax.symbol],
range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3))
+ [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 3))
}
matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
+ [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 2))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3))
+ [.foregroundColor: NSColor.Syntax.symbol], range: match.range(at: 3))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.number], range: match.range(at: 4))
+ [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 4))
}
matches = blockerRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
+ [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 2))
}
matches = noteRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+ [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 2))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
+ [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 3))
}
matches = stageRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+ [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 2))
}
matches = groupRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
textStorage.addAttributes(
- [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
+ [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 2))
}
}
}
struct MapTextEditor: NSViewControllerRepresentable {
@Binding var document: MapDocument
+ var highlightRanges: [Range<String.Index>]
+ var selectedRange: Int
var onChange: () -> Void = {}
func makeNSViewController(
context: NSViewControllerRepresentableContext<MapTextEditor>
) -> MapTextEditorController {
- return MapTextEditorController(document: $document, onChange: onChange)
+ return MapTextEditorController(
+ document: $document, highlightRanges: highlightRanges, selectedRange: selectedRange,
+ onChange: onChange)
}
func updateNSViewController(
_ nsViewController: MapTextEditorController,
context: NSViewControllerRepresentableContext<MapTextEditor>
- ) {}
+ ) {
+ nsViewController.highlightRanges = highlightRanges
+ if nsViewController.selectedRange != selectedRange {
+ nsViewController.selectedRange = selectedRange
+ }
+ }
}